Repository navigation
Develop - #406
Merged
Merged
Develop#406
Conversation
The cli job built cli/ alone, so Maven resolved linkeddatahub-rdf at the release version from Central. release.sh deploys the library only after pushing the tag, and Central publishes it minutes later, while the job starts seconds after the push: it failed for 6.0.1 and 6.1.0, and since it is the job that creates the GitHub release, neither got one. 85fde19 taught the three test workflows to install rdf/ from the checkout first and missed this one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…t block of its own (#407) * An assistant drawer: a question becomes a Web-Algebra plan, shown before it runs The drawer on the right edge of every page for a signed-in reader sends a question in natural language to the web-algebra sidecar's plan service, with the document the reader is on, its dataspace's SPARQL endpoint and the earlier turns. The plan comes back as its summary, the operations it invokes and the XML itself, and runs only on Execute (or at once with "Execute by default"); the steps report as the executor does, the result set or the documents written are shown, and the page catches up. Each card keeps its plan and, once run, what it returned, so a follow-up that refers to "them" is planned on that result as a VALUES block rather than on a new query. A Clear button empties the log and both stores. LDH's operation family is addressed as `waldh:` elements. The stack gains the web-algebra service and nginx's reserved /webalgebra path with the caller's certificate; OPENAI_MODEL picks the planner's model. The chat section of ldh.css parks the core Drawer under the sticky header. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * Serve the agent guide at /AGENTS.md, and say in it that the default graph is empty A client writing a query against a dataspace's endpoint has to know that every document is a named graph and a pattern outside GRAPH matches nothing; the assistant's SPARQLString reads an endpoint's AGENTS.md as service documentation and was answering "no results" for want of it. The repository's guide is shipped in the WAR from where it lives, mapped to the default servlet beside robots.txt and sitemap.xml so it is public and never enters the dispatcher, served as text/markdown, and gains the named-graph paragraph in its Querying section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The assistant is part of the dataspace's panel, and its rows name what they return The drawer moves inside #tab-body, beside the panes and under the dataspace tab bar: the header and the tab bar keep the full width, the drawer starts where the panel does, and its head takes the action bar's height so the two seams are one line. An open drawer insets the panes and the footer rather than the frame. The create dock's full-bleed margin and padding, computed on the viewport, slid its buttons under the drawer; they now share --ldh-pane-w, the panes' width, which the open drawer narrows. A result set's rows get their labels: the distinct URIs are looked up with the two metadata queries a view block runs, against the endpoint the plan queried, and the rows are drawn again with the metadata tunnelled to the link leaf when the labels land. The UI spec measures the panes and the seam, and expects the written container by its title. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The model key is an optional secret, declared in the override like the OAuth ones The base compose file no longer binds secrets/openai_api_key.txt, so a stack without a key - the test workflows, a deployment that only runs plans - comes up; a deployment that writes plans declares the openai_api_key secret and OPENAI_API_KEY_FILE in its override, which is where the dev stack now has them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The assistant hands the planner the dataspace's ontology, and the agent guide says a join crosses documents The dataspace's ontology, which every document advertises with Link rel=lds:ontology, is the vocabulary its domain data is described in: labels, domains and ranges the planner's induced schema lacks, and none of the documents' plumbing the induced one picks up. The client now keeps the Link target the way it keeps the endpoint, lds:ontology() reads it, and the question goes to the plan service with it, so a SPARQLString against this dataspace is written from GET of the ontology rather than from ExtractOntology (REST-VKG #26). The guide the query writer reads from /AGENTS.md gains the two rules the Northwind evaluation showed the model breaking: a resource is described in the document about it, so a pattern that follows a link to another resource needs a GRAPH of its own for each; and a property is used on instances of its domain, so what the ontology declares on Person is reached through the corporation's employee, not written on the corporation. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * An ASK is answered with a boolean, which the assistant's query check depends on The SPARQL endpoint answered an ASK with an empty result set, so the Web-Algebra SPARQLString check - an ASK with the query's own pattern, to send a query that matches nothing back to the model - never bit against this platform. The boolean result is Core 5.0.6: SPARQLResult, its provider, and the ASK path of SPARQLEndpointImpl. Core is pinned at 5.0.6-SNAPSHOT ahead of Web-Client's transitive 5.0.5, and SPARQLResultProvider is registered beside ResultSetProvider on the server and on both clients. The pin resolves from a local install only until Core is released, so an image build needs that release, or the snapshot deployed, first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * A plan the service refused reports as a failure in the drawer, with the service's reason An envelope holding a message instead of a plan was shown as a reply whatever the status, which read a validator's refusal - a parser's line and column - as the assistant declining in words. A message on a 200 is still a reply; on any other status it is the service's reason for failing, and the card now reports it as the failure it is, through the chain's own failure path, with that reason as the detail rather than the status line. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * A plan that stopped folds out the XML of the steps it did enter A step row borrowed its operation's XML from the plan only when the executor's steps and the plan's operations matched one to one, so a plan that failed at its tenth operation of nineteen rendered every row without a fold-out, and so did one still running - only a plan that ran to the end lined up. The steps are now paired with the plan's leading operations, as many as were entered, in the order the executor enters them; a ForEach's iterations are more steps than operations and still get none. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The assistant is a block in the content body, with its composer docked onto the create bar The assistant produces blocks, and the drawer kept it where blocks are not: a plan wrote into the document, then the whole page reloaded to catch up, in a panel beside the content it changed. The placement rule already said where it belongs - a form for what is added to the current document renders inline - so it moves there. The conversation is an ephemeral block at the end of the content body, before the create bar, so what a step writes lands above it in order; the composer is a panel docked onto the bar's upper edge, opened by the bar's own button beside Create and closed by it or by Escape. A page with no bar - a proxied resource, a search result - gets a bar holding the composer alone, so reads work everywhere. None of it is server-rendered but the button: a conversation exists only once the client runs, and the body it sits in is replaced on every navigation and every write's catch-up. So the conversation is not the DOM: each document's log is an element kept under the document's URI, the one composer form is kept for the session, and ldh:ChatMount moves them into whatever body was just rendered. A card is painted into its log whether the log is in the page or not, so a plan still running when the body goes finishes into its log and is there when the document is next shown; a card that reported a write comes through the catch-up with its steps, its documents and its fold-outs. Conversations are per document, and the history the planner hears is that document's. The drawer, its edge sensor and its CSS go; the mount is guarded on the WebID itself rather than the agent's document, which document() only has once something loaded it. The assistant spec covers the new surface: opened from the bar and docked above it, a card in the body, Clear from the block's head, and the card surviving the catch-up. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The composer is as wide as the content column, and starts where it starts Docked onto the create bar, the composer took the bar's full width: the bar is full-bleed on the left and padded on the right to the column's edge, so the composer ran from the viewport's edge to the Create button while the content it serves sits in a centred column. Its left margin now makes up the column's inset over the bar's own padding, and its width is the column's inside its padding; margin plus width fill the bar's line exactly, which is what wraps the buttons below it. The spec measures it against the column. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The assistant is a component of the app kit: ldh-chat- classes, the kit's block header, its skin in app.css The assistant had no place in the design system - no component, no card, no rule - so its markup could only be read off the XSL. It is one now (ui_kits/app/Assistant.jsx, preview/component-assistant.html), and the product uses the kit's vocabulary: the chat-* classes become ldh-chat-*, the block's head is THE block header at compact density with Clear in its actions cluster, and the rules that are the component's look - the create bar's, the block's, the turns', the plan card's, the step rows', the composer's - move from ldh.css into the vendored app.css, pushed upstream in the same change. What stays in ldh.css is the page's geometry: where the bar parks and how wide the docked composer is, which only this page's column and pane widths can say. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * The card answers in words, with the trace folded under it A plan's card showed its rows and its step tree, which is the machinery; the answer the reader asked for was theirs to read off it. Once a plan ends, and once its rows have their labels, the card asks the answer service beside the plan service for a reading - the question, the plan's summary, the steps, the documents written and the labelled rows go, a sentence or two comes back - and that sentence becomes the card's first line. The steps now live under a trace disclosure whose summary is their count and the plan's time: open while the plan is being read and run, since the rows are what there is to see, folded once there is an answer to read instead. The rows stay on the card and stay the authority; a service that cannot answer leaves the card as it was. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * A result is a block in the card, drawn by the plan's hint, and Keep makes it the document's A plan's result rendered as a bare table whatever it was. It now sits in a well of its own under the card, headed like a block with the mode's label, and drawn through the document's own block rendering by the plan's presentation hint: a result set as the results table or, when the plan said chart, as a chart drawn into the card with the hint's axes; a graph as the view block's list, grid or table. What the reader sees in the card is what a block in the document would show. Keep, in the well's actions, writes it there: a plan of the card's own making - the query stored on the document, the endpoint registered as a service first when it is not the dataspace's own, a view in the mode drawn or a chart with the axes drawn, and an object block placing it after the document's last block - run at once as its own card. A document the assistant wrote to comes back in ContentMode, which is the mode that shows its blocks, so the kept block appears above the conversation. The fragments carry their '#', since the waldh: operations resolve <fragment> against <url> as a relative URI. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * A write's report is not a result, and the folded trace shows it opens and how it went Every write answers a one-row ?status ?url result set, and the card drew each one as a result well with Keep, sent them to the answer pass as the rows - which then said no value was returned - and kept them as the next question's "them". They are the documents written, which the card already lists, so they are left out of all three, and the list counts each document once. The trace folded under a summary that read as a caption: no chevron, no hover, and the green rows seemed to vanish. The summary now has a chevron that turns when open and the outcome's glyph, a green check or a red error, so the folded trace still says how the steps went. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Keep goes: a result reaches the document by asking for it Keep pinned a result into the document as a view or chart built from the card's query. It chose the block by the card's rendering, and turned "what's the current year" into a view over SELECT (YEAR(NOW()) AS ?currentYear), which a view - a list of resources - cannot show, and whose block then failed to render at all. With chats about to be stored in the document, every result persists anyway, and putting one into the document's flow is a request in words that the planner writes as blocks. The button, its write plan and its translations are removed; the result wells stay. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * A view lists what an aliased first variable binds, and says when it binds no resources SPARQL.js gives an aliased projection, (?s AS ?resource), as a map rather than the string "?s", and reading only the string left the view's focus variable empty and its whole render failing. ldh:first-var-name reads both shapes. An alias is bound only in the projection, so the rewrites that project something else or append patterns to the WHERE (the result count, the container lookup, the parallax step) first wrap the query as a subquery (ldh:wrap-subquery). Without it the container lookup joined on an unbound variable, matched every triple in the store and exhausted Fuseki's memory. The count's projection and the parallax step's WHERE now match the outer query only, not a subquery's. A first variable that binds values, (YEAR(NOW()) AS ?year), gives rows the DESCRIBE finds nothing for. The count tells the two apart, and the block says the query lists no resources rather than that nothing matched. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * A conversation is a block: each chat has its own composer, is stored turn by turn, and shows its plans resolved The assistant moves from one ephemeral, client-kept log docked on the create bar to an ldh:Chat resource placed by an ldh:Object block, like a view or a chart. A page holds as many chats as it has chat blocks, each ending in its own composer; the bar's Assistant button starts a new one before the bar, which is written - chat, object block and the document's next rdf:_N, in one conditional POST - on its first question, so a click that asks nothing leaves nothing behind. Every turn is an ldh:ChatTurn, the chat's next rdf:_N, written to the chat's own document once its answer lands: the question, the answer, the plan and what the execution reported (result capped at 100), as XMLLiterals, and the outcome. The write catch-up waits for it, and the chat block's RowHook draws every turn from the store, so a chat survives leaving the page, can be embedded, and continues with its stored history. A reader without acl:Append sees the transcript and no composer. The literals travel escaped, typed rdf:XMLLiteral: Jena's RDF/XML parser refuses an rdf:RDF inside rdf:parseType="Literal", and a turn whose plan read a graph answered 400. The trace shows the plan as it ran: a step that reported a value at run time (the query a SPARQLString wrote, as wa:value) folds it out, and the operation around it shows the call replaced by that value. ldh.ttl and the rdf/ vocabulary gain ldh:Chat, ldh:ChatTurn and the turn's properties; the global mount, the session composer and Clear are gone. tests/ui/specs/shell/assistant.spec.mjs is rewritten around chat blocks (8 specs, 5 without a model). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * The assistant reads how to show a result as the client's mode URIs, and draws a grid and a timeline A plan's <wa:present> now names the layout mode as ac:mode - ac:TableMode, ac:ListMode, ac:GridMode, ac:ChartMode, the values a view block's ac:mode takes - and a chart's details as the properties a chart block has: ldh:chartType, ldh:categoryVarName, ldh:seriesVarName. ldh:present-mode and ldh:present-chart-type read them, and still read the table/list/grid/chart tokens and the type/category/series attributes of turns stored before, so those draw as they did. A result well carries its mode's URI in data-mode. Specs: a stubbed grid plan whose graph depicts its resources draws one card per resource with its image, live and again from the store; a stubbed timeline plan draws one labelled span per row, the one whose end came from COALESCE included. Both run without a model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * An operation's XML fills its code field The trace folds out each operation's XML in the kit's code field, which is a flex row built for the kit's own text area, the one child that grows. The trace puts a plain pre there, which nothing told to grow, so a plan of short lines showed its XML in a box as wide as its longest line inside a field as wide as the card. The pre now takes flex: 1 and min-width: 0, as the text area does; long lines still scroll inside it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * The assistant spec passes the admin base positionally, as ldh takes it since #404 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Core 5.0.6 arrives through Web-Client 6.0.4, so the snapshot pin and the overlay exclude go The boolean result of an ASK, which the assistant checks its queries with, shipped in Core 5.0.6 and Web-Client 6.0.4 depends on it. The direct core:5.0.6-SNAPSHOT dependency, which only resolved from a local repository, and the overlay's exclude of its own Core jar are no longer needed; the SPARQLResultProvider registrations compile against the released Core. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * A bar chart's value axis starts at zero, a chart's Save hides with its controls, and a maximised graph fills the viewport - chart.xsl: a bar's length is its value, so the bar chart's value axis takes minValue 0 (ignored when the data goes below it) - app.css: the chart's Save stores what its controls picked, so its footer is hidden while the controls are collapsed - ldh.css: a maximised 3D graph canvas is pinned with inset: 0 and auto size, rather than 100% of a containing block that is not the viewport Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
…wherever it is rendered A chart, a map and a 3D graph render into a canvas a library draws on, and each place that rendered one started it by hand: a document in its mode (client.xsl), an object block in its mode (object.xsl, whose copy said "TO-DO: reuse similar initialization code from client.xsl"), a view switched to it (view.xsl), a chart block's three control handlers and its results response (chart.xsl), the query block's chart (query.xsl) and the assistant's (chat.xsl). Now the canvas element is the thing dispatched on: ldh:InitCanvas, one template per canvas class. - chart-canvas (chart.xsl): the data table from a graph or a result set, by templates on rdf:RDF and srx:sparql (ldh:ChartDataTable), the chart type, category and series the caller gives or a table of every property or variable (ldh:ChartSeries), the data table kept on the caller's cache. - map-canvas (map.xsl): the OpenLayers map kept on the cache, created the first time and moved onto the canvas after; features from the view's query when there is one, else from the graph. - graph-3d-canvas (graph3d.xsl): the graph started the first time and fed new results after, so a view coming back to it redraws the same graph; graph-results lets an object block show its whole document. A canvas emitted just now falls back to 600px, having no height yet. Callers apply the mode to the canvases they rendered, with the results, the cache and what else the canvas needs as tunnel parameters. The chart block's three onchange handlers are one; the view's three mode branches are one call. Specs: view/modes.spec switches a view to chart, map and graph (and away from the graph and back); object.spec draws an object block in each canvas mode. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…el when there is none The coverage inventory names the create bar's Assistant button instead of the retired chat drawer. The assistant's model-backed tests skip when the plan service answers 503, its reply when no model key is configured, as on CI; any other failure still fails them. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…es whole milliseconds The http-tests override moves the secretary keystore and the server certificate under tests/http/ssl for nginx and linkeddatahub but left web-algebra on the repository root's ssl/, empty on a CI checkout: the servlet failed to start and every /webalgebra request answered 500, so the assistant's model tests could not see the 503 they skip on. The data table converters computed a datetime's milliseconds in floating point, giving 984.9999999999999 for .985 on SaxonJS. Google Charts parses a Date(...) string only when its numbers are whole, and threw on the string it kept instead. Rounded. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e block around it A property list nested in a value cell queried the enclosing block, so the narrow-container collapse never fired while every level spent 264px of fixed track. Schema.org JSON-LD three blank nodes deep (a Rotten Tomatoes filmography) left the innermost value 84px wide, one word per line. The nested list is now its own query container and fills the cell's flex row. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…result set An upstream's SPARQL results content type does not say whether the body holds rows or the boolean an ASK answers with, and reading a boolean as a ResultSetRewindable threw. The results branch now reads a SPARQLResult and serves a boolean in the result set formats that have a boolean writer. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The proxy replaced its own Vary with the upstream's, so an upstream sending Cache-Control without Vary: Accept left the proxied RDF cacheable under the bare ?uri= URL: varnish-frontend served it to the next anonymous browser navigation, which downloaded RDF/XML instead of getting the shell. Rotten Tomatoes (max-age=300, Vary: Accept-Encoding) and schema.org (max-age=600, no Vary) both did it. The local and upstream values are now merged, always including Accept. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nto a 500 Every exception mapper negotiates its response through Request.selectVariant(), which parses Accept, Accept-Language, Accept-Charset and Accept-Encoding, and a parse failure there replaced the mapper's status with the container's 500. On linkeddatahub.com that was crawlers replaying ?accept=application%2Fld+json, whose + decodes to a space: ApplicationFilter copied "application/ld json" into Accept, and the 403 for the protected page came back as a 500, about 65 a day. AcceptHeadersFilter replaces AcceptLanguageFilter and drops any of the four headers that does not parse. ApplicationFilter refuses an accept parameter that is not a single media type with 400 before touching the header; that also covers ?accept=foo, which Jersey reads as foo/* but getWritableMediaTypes() failed on with a 500. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…takes the dataspace down ontapi files every graph of an imports closure under the ontology IRI its header declares, and when that differs from the URI it was imported by, it writes an owl:imports of the declared name into the importing graph. That graph was the raw copy in the shared ontology repository, so the write outlived the closure: the next rebuild over the same cache resolved both names, the declared one into a placeholder for a URI that did not dereference to the document, and ontapi refused the second graph of that name. Every request to the dataspace failed with "Another graph with name ... is already in the hierarchy" until the cache was cleared. The upload test left such an import behind (an upload declaring https://example.org/test#). ScopedGraphRepository now hands ontapi the shared graphs wrapped in a Delta, one per graph instance, so its writes stay with the closure; the root ontology is read through the view as well. A graph resolved by one URI that declares another is logged. Saving an ontology whose owl:imports names a document declaring another ontology is rejected with 422, naming the declared IRI. The check validates the whole representation, as SPIN and SHACL constraints do; an unresolvable import, or one without an ontology header, is let through. The upload fixture declares its ontology relative to where it is uploaded (<#>) and the test imports the upload's hash URI; a new test covers the rejection. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… declare An owl:imports names an ontology, and SP, SPIN and OWL name themselves without the hash their terms carry: <http://spinrdf.org/sp>, <http://spinrdf.org/spin>, <http://www.w3.org/2002/07/owl>. The sp:, spin: and owl: prefixes expand to the term namespaces, so ldh.ttl, dh.ttl, def.ttl and lds.ttl imported names no document declares, which the closure warned about on every build and an ontology saved the same way is now refused for. lacl.ttl, adm.ttl and spin.ttl already imported the declared IRIs. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ldh:send-request exists only on the server ldh:query-result gets an implementation per engine, chosen by use-when: on the server it POSTs the query as application/sparql-query through the ldh:send-request extension function, on the client it keeps the document() GET. Every direct ldh:send-request call in document.xsl and layout.xsl becomes an ldh:query-result call, and the stub declaration is gone: shared code no longer compiles a call that silently returned nothing in the browser, and one that slips in fails the SEF compile instead. ac:value-intersect and ac:value-except are removed. Every caller compared the keys of two triple maps, which map:contains() answers directly, without distinct-values() or a nested general comparison. Verified on the local stack with the UI specs memento, document-form, row-form, prop-list, object and lang-field (18 passed). constructor-editor was not verified: the stack runs the dev config, which lacks the packages.localhost dataspace the suite publishes its fixture package to, and with the public package substituted the specs fail on the card's data-graph, before any save. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…howing a 502 The proxy answers 406 for such a page - it has no RDF representation, which is what the caller asked for - where it used to answer 502, as if the origin had failed. The client opens a 406 for an external document the way a target="_blank" link would, with no opener, and navigates there itself when the popup is blocked. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
All globals share one namespace across the import tree, where Web-Client and package stylesheets declare their own unprefixed ones, so an unprefixed LDH param could be overridden by accident. Overriding $ldh:select-labelled-string and its peers is now deliberate, and the prefix marks who owns the hook. The button/helper-text class params in client/form.xsl keep their names: they override rdfa-editor/classes.xsl by name. Template-local params of the same name (page-size in navigation.xsl, location-mapping in layout.xsl) are unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…wn as a map or a 3D graph The assistant's result wells had a mode switch of their own (ldh:ExecutionResult) that knew a table, a list, a grid and a chart, and drew only a chart canvas, by id. A well now renders its result through ldh:ViewModeChoice, the switch every view uses, and its canvases are drawn as every canvas is: the class-keyed ldh:InitCanvas sweep over the well. A result set is a table or a chart; a graph is a list, a grid, a table, a map or a 3D graph, by the plan's hint (ldh:ResultMode). Each well is named after its result's place in what the plan returned, so its canvases are named apart, any number to a page and pane. Labels arriving for a result set redraw its well the same way, a chart's included. - ldh:ViewModeChoice takes no $select-xml for results with no query of their own, and its table mode draws a SPARQL result set (srx:sparql in ldh:TableViewBlock). - $ldh:canvas-classes (resource.xsl) maps a layout mode to its canvas class; the pane, object-block and assistant sweeps read it instead of each listing the classes. - The well's icon is the shared $ldh:mode-icons entry; the chat's own CSS for its table and chart canvas gave way to the well's (synced to the design system's ui_kits/app/app.css and Assistant.jsx). - Shared modules no longer name the assistant in their comments. Verified on the localhost:4443 dev stack (make sef, make up -- --build, web-algebra rebuilt from REST-VKG feat/generic-plan-checks): tests/ui assistant.spec.mjs - the model-free specs, including a new one that asks for a map and then a 3D graph in one chat, draws both canvases on one page with distinct ids, and draws them again from the stored turns after a reload - and the map, graph3d, object, chart, query and view block specs all pass. Not run: the concept-hierarchy, view create and view empty specs, which need the taxonomy package this stack has no packages dataspace to publish, and the specs that ask the model. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e obsolete string-language Model.read Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d to ignore shutdown A target with a filter registered on it, such as the WebID delegation filter, runs on a connector of its own over the client's pool, and Jersey closes that connector once it is garbage-collected - which, unless the pool is shared, shuts the pool down under every other request (jersey#4449). The three pools in getClient, getNoCertClient and getVerifiedClient guarded against it by overriding shutdown() to do nothing and close() to shut down. They now set ApacheClientProperties.CONNECTION_MANAGER_SHARED, the setting Jersey provides for a pool the application owns, and drop those overrides. Nothing closed these pools deliberately, so no shutdown path changes. The releaseConnection override stays: the reuse of a connection that presented a client certificate (jersey#2855) has no setting. ConnectionPoolTest runs 20 requests per client, each through a target with its own filter and with the collector run between them, against local servers: getClient over HTTPS asking for a client certificate (the certificate is presented, its one connection reused), getNoCertClient over the same server (none presented, one connection), and getVerifiedClient over plain HTTP, since it trusts the JVM's CAs only. The same test passes against the previous subclass, and with neither every request after the first collection fails with "Connection pool shut down". The keystores under client/ssl are self-signed, for localhost and a test agent, valid for 100 years. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the top of the page Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e assistant landed Web-Client 6.0.5 brings the proxy's ASK boolean (AtomGraph/Web-Client#98) and drops the unused JSONLDWriterAdapter (#99). Built with make sef against it: client-6.0.5-classes.jar is the one Web-Client jar in the WAR and the client stylesheet compiles. The Unreleased section gains the stylesheet migrations (the ldh: prefix on top-level params and variables, ldh:query-result), the assistant's map and 3D graph, the proxy, Accept and ontology import fixes, and the Web-Client bump. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…iption, advertising queries only Core 5.0.7 answers a query-less GET on the SPARQL endpoint with its SPARQL 1.1 Service Description instead of a 400. LDH narrows the supported languages to sd:SPARQL11Query, since writes go through the Graph Store Protocol. The Core endpoint constructors now take UriInfo; Namespace passes its own and drops the duplicate field. <ns> keeps its query-less behaviour (the namespace ontology on end-user, 400 on admin). Web-Client 6.0.6-SNAPSHOT, which carries Core 5.0.7-SNAPSHOT. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…mit baseline is not cut short Two intermittent UI failures, both the specs' own timing: - code-block: the prose block sits below the fixture page's view, which expands when its results arrive. A hover before then landed on the code block, which the view pushed out from under the pointer onto its rows (reproduced locally with the view held back 1.5 s: y 263 -> 818, opacity back to 0). lib/settle.mjs gains loaded(), which waits until no block carries aria-busy; the spec opens the page through it before any hover or click. - rate-limit: the page's two views each send the same /ns parallax label lookup, so every load sends it twice. The baseline stopped recording at networkidle, which the second lookup can follow - measured 38 ms after it on a 4x throttled CPU - so it counted one, and the refused load, which waits much longer, "over-fetched" the second. quiet() ends both loads the same way: after networkidle, until no block request has gone out for 2 s. Verified locally: code-block 15/15, rate-limit 8/8 including 4 runs at 4x CPU throttle. The rate-limit race could only be measured here, not forced to fail. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d of showing the URI's fragment ldh:form-horizontal-submit-success redrew the row from the submitted triples alone, which carry a resource value as its URI, so a category saved on a product read "this" until the page was reloaded. The redraw now runs the object-metadata chain over those triples first, as every other block render does, and passes the labels to ldh:BlockRow; if the label query fails, the row is drawn without them as before. row-form.spec.mjs covers the inline form's save paths: a changed resource value is labelled after the save, a constraint violation keeps the form open with the field marked and the document unchanged and then saves once corrected, and a save over a document changed since the form opened is refused 412 with the failure shown in the form. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…restarts (#412) * SPARQL endpoints are capped by concurrency, and a Fuseki out of heap restarts instead of running on broken. nginx keys three zones on an external client's /sparql request only: at most NGINX_SPARQL_CONN_PER_CLIENT (2) in flight per client, NGINX_SPARQL_CONN_TOTAL (8) across all of them, and NGINX_SPARQL_RATE (2r/s, burst 10), each answered with 429. Every other path and every internal caller maps to an empty key, which nginx does not count, so the platform's own queries and the generic locations are untouched. Rate limiting alone let a few heavy queries arriving slowly exhaust the store's heap together; the query timeout bounds their time, not their memory. Fuseki gets -XX:+ExitOnOutOfMemoryError in JAVA_TOOL_OPTIONS, which deployment overrides of the heap leave intact, so an exhausted heap ends the JVM and restart: on-failure brings it back. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Adversarial SPARQL load tests: the concurrency cap and OOM restart, proven under load Three scenarios drive query load through the front door and watch what it does to the platform, each asserting the behaviour PR #412 adds, so they pass with its guards and fail without them: - sparql-heavy-burst-site-stays-up.sh: more long queries at once than there are request threads. Without the concurrency cap every thread ends up on a query and the site stops answering; with it at most NGINX_SPARQL_CONN_TOTAL are in flight, the overflow is 429, and a visitor fetching the root is still served. (Measured: exactly 8 reach the 60 s timeout, the rest 429, the probe never drops.) - sparql-oom-fuseki-recovers.sh: a query that materialises a large intermediate result reaches OutOfMemoryError against the stack's small heap before the query timeout. ExitOnOutOfMemoryError + restart ends the JVM and brings it back, so the store answers a trivial query again within the recovery window instead of running on broken. - sparql-flood-rate-limited.sh: many trivial queries as fast as possible. The request-rate limit sheds most as 429 and the site keeps answering. The query load comes from `adversary` containers on a 100.64.0.0/24 network (carrier-grade NAT, which the front door's RFC 1918 exemption does not skip), so each is a distinct external client to the caps; the site probe runs from the host (exempt, like a real internal check). The dataspace is made public first - anonymous /sparql is the surface under test. Fuseki's heap is dropped to 512m so the OOM path is reachable; the queries themselves need no seeded data (self-joins of the empty store's own quads). tests/load/compare.sh measures the scenarios across two or more revisions in MEASURE_ONLY mode for a before/after. The CI workflow builds the ldh CLI, makes the dataspace public, and points the suite at the running stack via COMPOSE_CMD. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Load-tests: set up JDK 21 before building the ldh CLI The make-public step needs the ldh CLI, built with `mvn` in the job; ubuntu-latest's default JDK is older than the project's release 21, so the rdf build failed with "release version 21 not supported". Set up Temurin 21 first, as the HTTP-tests workflow does. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Load-tests: drive the SPARQL burst from one loop per worker, not one exec per request attack() spawned a `docker compose exec` for every request. On a 2-core CI runner the burst buried the box under compose-CLI launches and starved the very stack under test, so the suite ran for tens of minutes instead of its nominal duration. Each worker now runs a single back-to-back curl loop inside its adversary container for the whole duration - one exec per worker, the request rate paid in-container. The scenarios are unchanged (heavy burst still sees exactly the 8-query cap reach the timeout, the OOM store still restarts and recovers, the flood is still shed as 429) and now finish in about their nominal wall time. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Load-tests: neutral naming, tunable thresholds, firmer assertions, robust output Follow-up polish on the SPARQL load tests: - Rename throughout to plain load-testing terms (loadgen service/network, run_load, cpu-bound/ memory-bound queries, sparql-concurrency / sparql-rate-limited / sparql-heap-fuseki-recovers) and neutral wording in comments and messages. No behaviour change. - run.sh readiness wait now accepts 200 as well as 403 (the dataspace is public by the time the suite runs) and is bounded at 300 s, so a stack that never comes up fails instead of hanging. - Thresholds are env-overridable with documented defaults: CONC, DURATION, PROBE_BOUND (the concurrency test's answer deadline) and MIN_REJECTED_PCT (the rate-limit test's shed share). - Firmer assertions: the concurrency test checks the slowest probe against PROBE_BOUND and names the configured cap; the rate-limit test requires a minimum rejected percentage rather than merely more-429-than-200; the heap test now asserts the container actually restarted (the guard fired), not only that it ends up healthy. - run_load writes one file per worker and concatenates, so result lines can never interleave. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Load-tests: queries that hold their slot, assertions that pin each guard down The cpu-bound query counted the stock 24-quad store's five-way join in ~4 s, so it never reached the 60 s timeout and the concurrency test passed without pressing on the cap. Six-way runs past it with a flat heap. The concurrency test now rebuilds from each response's start and end how many queries were in flight at once: the total must reach NGINX_SPARQL_CONN_TOTAL and never pass it, no client may pass its per-client cap, and a rejected worker backs off instead of spinning into the rate limit. The rate test sends one worker per client, so the concurrency caps cannot answer, and bounds each client's accepted requests by burst + rate x window. The heap test requires the JVM's "Terminating due to java.lang.OutOfMemoryError", so a container killed at its memory limit no longer passes as the guard. curl writes its -w line on failure too, so the "|| echo 000 0" fallbacks recorded every failure twice. run.sh waits for the platform before each test, not only the first, and no longer runs compare.sh as a test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Load-tests: drop compare.sh, the before/after measurement for this one change Once the guards are on develop there is no "before" left to measure against, and the three tests assert the guards on every CI run. The MEASURE_ONLY exits served only compare.sh, and run.sh no longer has to skip it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… short The first block a write added was picked with [1] on the last step of the path, so it applied per member and returned every new block. An assistant write always leaves two - the chat's own block, written by its first question after the page was loaded, and the one the plan added - and the zero-or-one variable refused them: the reload stopped once the body was drawn, with no breadcrumb, no scroll and the chat card never finished. The path is parenthesized, and a block placing a chat is not taken for what the plan wrote. A spec answers the plan service and the executor itself, the executor's write made with the CLI, and asserts the reloaded page keeps its breadcrumb, shows the added block in view and finishes the card. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
9fbe050 redraws a saved row once its values' labels are in, rather than at once. ldh:form-horizontal-submit-success then calls rdfae:hide-overlay, which returns focus to the editor's active block, and that editor was still in the page: the redraw used to have removed it already. When the labels arrived, the redraw removed an editor holding focus. Chrome fires focusout for a focused element being removed (Firefox does not), the XHTML autosave took it for the author leaving an unchanged block and cancelled it, the redraw replaced a node the cancel had moved, the fallback render failed on the detached block, and the new block vanished from the page although it was saved. The save is the editor's exit, so its active block is cleared before the overlay is hidden and focus does not go back into it. The labelled, delayed redraw of 9fbe050 stays. xhtml.spec.mjs adds a block in Content mode, types a sentence and clicks outside, then asserts the sentence is on the page in read mode, still there two seconds later, and stored. It failed on develop with the removeChild page error; with the fix it passes, as do row-form, annotation-dialog and the other block specs (88 passed). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…op stylesheets receiving credentials (#415) * /context publishes the dataspaces and their admin pairing, and stylesheets no longer receive the context dataset $lds:Context passed the whole context dataset to every server-side transformation: the dataspace descriptions, but also the SPARQL services behind them, their backend endpoints and the credentials for them. Every module of the composed stylesheet could read it, package stylesheets included. PublicContext copies a whitelist of the lds:Dataspace descriptions only (type, title, description, origin, ontology, stylesheet, lds:public) and adds lds:admin to each end-user dataspace: the base URI of its administration application, derived by the server from the admin subdomain as before. It is served at /context on every dataspace, public through seeded authorizations on both origins, and the stylesheet param is gone. End-user responses also link their admin application with rel="lds:admin", so that no client derives it from the URI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Stylesheets read the dataspace types and the admin pairing from /context instead of the admin. subdomain lds:context() has a server twin that dereferences /context and a client twin that reads the copy the bootstrap preloads, and lds:dataspace-description() moves into the shared library. The "is this the admin app" checks test lds:AdminDataspace, the admin URL comes from lds:admin, and the admin app finds its end-user ontology through the dataspace that names it as its lds:admin, so ldh:parent-origin goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * ldh finds the admin application from the end-user base, installs a namespace ontology from a file, and waits for imports The admin commands take the end-user base and follow its lds:admin link, falling back to the base as given, so a script no longer prefixes admin. to the host; URIs.adminBase goes. --proxy connects to the proxy but keeps the logical Host, which is how nginx routes dataspaces, so no admin proxy is derived either and URIRewriter goes. admin import ontology --file installs a local file as the namespace ontology in one step: the ontology IRI from the lds:ontology link, the namespace document replaced keeping its seeded header, the cache cleared. It replaces the reset PATCH, the POST with an @base and the clear that every app scripted. import csv|rdf print the import they create, and --wait waits until it has ended. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The changelog, CLAUDE.md and the CLI README describe /context, lds:admin and the new ldh options Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The import commands' target is described as the document the import is added to Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The admin dataspace's /context is publicly readable The admin ACL query honours public read authorizations only for an allow-list of URIs (the namespace, sign up, WebID profiles), so the seeded public-context authorization had no effect on the admin origin and its /context answered 403 - which also stops the client-side stylesheet, since the bootstrap preloads it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The bootstrap preloads /context as RDF/XML Its fetch sends no Accept of its own, so */* negotiated the HTML representation, which is not XML: the document pool's parse failed and the client-side stylesheet never started. The preload asks for RDF/XML with the accept parameter, as the namespace documents beside it do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * /context negotiates over the model media types, so */* no longer selects the TimeMap writer Returned without a variant, the model went to whichever Model writer Jersey found first for */*, which was the link-format TimeMap writer, and that writer only handles a TimeMap: a 500. The variants are the registered model media types, as for the documents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * GET-context.sh asks with Accept */* too, as the browser's preload does Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… the documents do (#416) Both returned their model with Response.ok() - /context selecting its variant by hand since the last commit, /settings not at all, which left it open to the same */* 500 through the TimeMap writer. Core's Response negotiates over the registered model media types and adds the per-variant entity tag, Vary and the precondition evaluation; every Dispatcher endpoint that returns a model now goes through it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…efined view no longer flashes its own metadata before its results Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…414) * An ldh Claude Code plugin teaches agents to build dataspaces, datasets and apps with the CLI The plugin (plugins/ldh/, published by the marketplace in .claude-plugin/) has four skills: dataspace provisions a dataspace in config/, dataset writes the namespace ontology and imports CSV/RDF (or builds a large dataset standalone with CSV2RDF, JSON2RDF or Saxon and bulk-loads it), app declares documents, views and charts and installs them with ldh push, and package writes an XSLT package against the open modes in hooks.xsl and publishes it to a registry. plugin.json carries the platform version: release.sh and make cli-version set it along with the rdf/ and cli/ poms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The ldh skills install the namespace ontology with --file, wait for imports with --wait, and check /context ldh admin import ontology --file replaces the reset PATCH, the POST with an @base and the clear, and the admin commands follow the end-user base's lds:admin link, so no skill derives an admin. URI; imports print their URI and --wait replaces the polling loop; the dataspace skill verifies a new dataspace in /context, and the 403 pitfall of clearing against the end-user base is gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The dataset skill's bulk-load pitfalls: JENA_HOME must point at 5.6, and make public can race Varnish Verified by running the standalone path end to end against a fresh stack: 5.6's arq still loads 6.x jars when JENA_HOME names a 6.x install, arq has no --results=trig, and make load's make public can reach a Varnish cache still restarting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The ldh plugin has a README with install steps, the skills and example requests Claude picks a skill from its description, so a user never has to name one; the examples show what to ask, two of them the prompts the skills were verified with. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * The ldh skills declare a viewed property's range, and check that ldh matches the instance Found by a fresh session following the skills end to end: the author page's inverse view never appeared, because a property view shows only on the types in the property's rdfs:range (or rdfs:domain for ldh:view) and the dataset skill's example declared none; with the range the page lists the books. The session also met the published image, older than the CLI, which advertises no lds:admin link, so admin commands answered 403 for the end-user base; every skill now says to match the two and how to tell. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The next release adds /context, the lds:admin link, ldh admin import ontology --file and ldh import --wait, and breaks $lds:Context, the import commands' output and --proxy's Host handling - a minor release, as 6.1.0 was. The rdf/ and cli/ poms and the ldh plugin follow the platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he editor's pure-XSLT cm:c14n (#417) ldh:canonical-content stored Saxon's plain serialize() output since the xml-c14n-sync lib was dropped; it now runs cm:c14n on the wrapper div, which as the c14n apex declares the XHTML namespace once. View source shows exactly that stored literal. xsl/rdfa-editor/ is synced from RDFa-Editor (canonical-xhtml.xsl, edit.xsl), and the stale xml-c14n-sync credit leaves the README. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…anded Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd the separated table border model An embedded card draws its hairline in the carrier's inner curve, read from --ldh-block-radius, which skins and the block-edges tweak now set. The `border-radius: inherit` it used reached only the DOM parent, which has no radius, so the hairline's square end stood proud of the carrier's corner in every skin. Tables use border-collapse: separate, since the collapsing model ignores border-radius on the table: its border drew square while the head cells rounded inside it. Row rules move onto the cells and the four corner cells round to the table's inner curve. Driven with Playwright on a ContentMode document: the embedded card's corners match the carrier's inner curve in m3, glass, glasspro, aurora, no skin, sharp and m3+flow; tables are separate with matching corner cells and row rules; the sticky head still pins; no dialogs or page errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.